home *** CD-ROM | disk | FTP | other *** search
- /*
- * For legal stuff see the file COPYRIGHT
- */
- #import <stdlib.h>
- #import <objc/hashtable.h>
- #import "ClientInfo.h"
- #import "Controller.h"
- #import "Session.h"
- #import "Expense.h"
-
- @interface ClientInfo(PRIVATE)
- - (void)setupSessionList;
- - (void)setupExpenseList;
- @end
-
- @implementation ClientInfo
-
- - init
- {
- [super init];
- [self setupSessionList];
- [self setupExpenseList];
- return self;
- }
-
- - free
- {
- [[sessionList freeObjects] free];
- sessionList = nil;
- [[expenseList freeObjects] free];
- expenseList = nil;
- return [super free];
- }
-
- - (void)computeTotalMins
- {
- int i, count = [sessionList count];
-
- totalMins = 0;
- for ( i = 0; i < count; i++ )
- totalMins += [[sessionList objectAt:i] minutes];
- }
-
- /*
- * Called by the controller when a new session has been started.
- */
- - addSession:session
- {
- [sessionList addObject:session];
- totalMins += [session minutes];
- return self;
- }
-
- - deleteSession:session
- {
- totalMins -= [session minutes];
- return [sessionList removeObject:session];
- }
-
- /*
- * Delete all session and expense data
- */
- - (void)deleteSessionsAndExpenses
- {
- [sessionList freeObjects];
- [expenseList freeObjects];
- totalMins = 0.0;
- }
-
- - (const char *)lastDescription
- {
- const char *str = [[sessionList objectAt:0] description];
- return str ? str : "";
- }
-
- - (int)sessionCount
- {
- return [sessionList count];
- }
-
- - sessionAt:(int)position
- {
- return [sessionList objectAt:position];
- }
-
- /*
- * Called by the controller when a new session has been started.
- */
- - addExpense:expense
- {
- [expenseList addObject:expense];
- return self;
- }
-
- - deleteExpense:expense
- {
- return [expenseList removeObject:expense];
- }
-
- - (void)deleteAllExpenses
- {
- [expenseList freeObjects];
- }
-
- - (int)expenseCount
- {
- return [expenseList count];
- }
-
- - expenseAt:(int)position
- {
- return [expenseList objectAt:position];
- }
-
- - (void)sortSessions
- {
- [sessionList sort];
- }
-
- - (void)sortExpenses
- {
- [expenseList sort];
- }
-
- - (const char *)shortName
- {
- return shortName;
- }
-
- - (const char *)clientName
- {
- return clientName;
- }
-
- - (const char *)contactName
- {
- return contactName;
- }
-
- - (const char *)street
- {
- return street;
- }
-
- - (const char *)city
- {
- return city;
- }
-
- - (const char *)addrState /* "state" is a defined method in Cell...*/
- {
- return state;
- }
-
- - (const char *)zipCode
- {
- return zipCode;
- }
-
- - (const char *)faxNumber
- {
- return faxNumber;
- }
-
- - (const char *)emailAddr
- {
- return emailAddr;
- }
-
- - (float)hourlyRate
- {
- return hourlyRate;
- }
-
- - (int)totalMins
- {
- return totalMins;
- }
-
- /*
- * This is necessary to have the number we use in calculations be
- * (almost) identical to that which is printed on the invoices. It
- * turns out for a total number of hours such as 76.466667, which
- * prints and displays as 76.47 causes enough error when multiplied
- * by an hourly rate that clients noticed the difference. (It happened
- * to be in their favor. This adjusts it to remove the discrepancy.
- * It happens to result in a few more cents in our favor, by the way!
- */
- - (float)totalHours
- {
- return ((int)((totalMins * 100)/60 + 0.5))/100.0; /* round up then truncate */
- }
-
- - (float)totalBillable
- {
- return hourlyRate * [self totalHours];
- }
-
- /*
- * Compute this on the fly.
- */
- - (float)totalExpenses
- {
- int i, count = [expenseList count];
- float amount = 0.0;
-
- for ( i = 0; i < count; i++ ) {
- Expense *expense = [expenseList objectAt:i];
- amount += [expense amount];
- }
-
- return amount;
- }
-
- - setShortName:(const char *)str
- {
- freeAndCopy( &shortName, str );
- return self;
- }
-
- - setClientName:(const char *)str
- {
- freeAndCopy( &clientName, str );
- return self;
- }
-
- - setContactName:(const char *)str
- {
- freeAndCopy( &contactName, str );
- return self;
- }
-
- - setStreet:(const char *)str
- {
- freeAndCopy( &street, str );
- return self;
- }
-
- - setCity:(const char *)str
- {
- freeAndCopy( &city, str );
- return self;
- }
-
- - setAddrState:(const char *)str
- {
- freeAndCopy( &state, str );
- return self;
- }
-
- - setZipCode:(const char *)str
- {
- freeAndCopy( &zipCode, str );
- return self;
- }
-
- - setFaxNumber:(const char *)str
- {
- freeAndCopy( &faxNumber, str );
- return self;
- }
-
- - setEmailAddr:(const char *)str
- {
- freeAndCopy( &emailAddr, str );
- return self;
- }
-
- - setHourlyRate:(float)value
- {
- hourlyRate = value;
- return self;
- }
-
- - setTotalMins:(int)value
- {
- totalMins = value;
- return self;
- }
-
- /*
- * Compress out consecutive sessions with identical descriptions.
- * This method assumes the list stores sessions in reverse
- * chronological order.
- */
- - (void)compactSessions
- {
- int i, earliestPos = [sessionList count] - 1;
- Session *earliest = [sessionList objectAt:earliestPos];
-
- for ( i = earliestPos - 1; i >= 0; --i ) {
- Session *session;
-
- session = [sessionList objectAt:i];
-
- /* Check for consecutive sessions on the same date with the same text */
- if ( strcmp([session description], [earliest description]) == 0 &&
- strcmp([session startDateString], [earliest startDateString]) == 0 ) {
- [earliest setMinutes:[earliest minutes] + [session minutes]];
- [[sessionList removeObjectAt:i] free];
- } else
- earliest = session; /* this is our new base case */
- }
- }
-
- - (void)exportToFile:(FILE *)fp
- {
- int i, count = [sessionList count];
-
- for ( i = 0; i < count; i++ ) {
- Session *session;
-
- session = [sessionList objectAt:i];
- fprintf( fp, "%s%c%s%c%s%c%d%c%s\n",
- shortName, DELIMITER,
- [session startDateString], DELIMITER,
- [session startTimeString], DELIMITER,
- [session minutes], DELIMITER,
- [session description] );
- }
- }
-
- - read: (NXTypedStream *) stream
- {
- extern int FileVersion;
-
- switch ( FileVersion ) {
- case 0:
- NXReadTypes( stream, "********f",
- &clientName, &contactName, &street, &city, &state, &zipCode,
- &faxNumber, &emailAddr, &hourlyRate );
-
- /* make the short name the same as the full name initially */
- shortName = NXCopyStringBuffer(clientName);
- break;
-
- default:
- /* Version 1 added shortName for client */
- NXReadTypes( stream, "*********f",
- &shortName, &clientName, &contactName,
- &street, &city, &state, &zipCode,
- &faxNumber, &emailAddr, &hourlyRate );
- break;
- }
-
- if ( ! sessionList )
- [self setupSessionList];
-
- [sessionList read:stream];
- [sessionList sort];
-
- if ( ! expenseList )
- [self setupExpenseList];
-
- if ( FileVersion >= 2 ) {
- [expenseList read:stream];
- [expenseList sort];
- }
-
- [self computeTotalMins];
- return self;
- }
-
- - write:(NXTypedStream *) stream
- {
- NXWriteTypes( stream, "*********f",
- &shortName, &clientName, &contactName,
- &street, &city, &state, &zipCode,
- &faxNumber, &emailAddr, &hourlyRate );
-
- [sessionList write:stream];
- [expenseList write:stream];
- return self;
- }
-
- @end
-
-
- @implementation ClientInfo(PRIVATE)
-
- /*
- * Compare the dates (and if equal, the times) of two
- * sessions and return the proper value to achieve a
- * reverse chronological sort.
- */
- - (int)compareSessions:(Session *)obj1 :(Session *)obj2
- {
- int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
-
- if ( date1 == date2 )
- return ( [obj1 timeValue] > [obj2 timeValue] ? -1 : 1 );
-
- return ( date1 > date2 ? -1 : 1 );
- }
-
- - (int)compareExpenses:(Expense *)obj1 :(Expense *)obj2
- {
- int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
-
- return ( date1 > date2 ? -1 : 1 );
- }
-
- - (void)setupSessionList
- {
- if ( ! sessionList ) {
- sessionList = [[SortList alloc] init];
- [sessionList setDelegate:self];
- [sessionList setAutoSort:YES];
- [sessionList useComparisonMethod:@selector(compareSessions::)];
- }
- }
-
- - (void)setupExpenseList
- {
- if ( ! expenseList ) {
- expenseList = [[SortList alloc] init];
- [expenseList setDelegate:self];
- [expenseList setAutoSort:YES];
- [expenseList useComparisonMethod:@selector(compareExpenses::)];
- }
- }
-
- @end
-